#pragma once
#include <iostream>
#include "log.hpp"
#include "dp.hpp"
#include "online.hpp"
// 定义棋盘大小
#define BOARD_ROW 15
#define BOARD_COL 15
#define CHESS_WHITE 1
#define CHESS_BLACK 2
// 游戏状态
typedef enum
{
    GAME_START,
    GAME_OVER
} room_statu;
class room
{
private:
    uint64_t _room_id;                    // 游戏房间id
    room_statu _statu;                    // 游戏房间状态
    int _play_count;                      // 房间中用户数量
    uint64_t _white_id;                   // 白棋玩家id
    uint64_t _black_id;                   // 黑棋玩家id
    user_table *_tb_user;                 // 玩家表
    online_manager *_online_user;         // 在线玩家
    std::vector<std::vector<int>> _board; // 棋盘
private:
    // 判断五星连珠
    bool five(int row, int col, int row_off, int col_off, int color)
    {
        // row,col是下棋位置,row_off是横向偏移量,col_off是纵向偏移量也是方向
        int count = 1;
        // 寻找一端
        int search_row = row + row_off;
        int search_col = col + col_off;
        while (search_row >= 0 && search_row <= BOARD_ROW &&
               search_col >= 0 && search_col <= BOARD_COL &&
               _board[search_row][search_col] == color)
        {
            // 同色棋子count++
            count++;
            // 检索位置继续偏移
            search_row += row_off;
            search_col += col_off;
        }
        return (count >= 5);
    }
    uint64_t win_check(int row, int col, int color)
    {
        // 从下棋位置的四个不同方向检测是否出现了5个以上相同颜色的棋子(横向,纵向,正斜,反斜)
        if (five(row, col, 0, 1, color) ||
            five(row, col, 1, 0, color) ||
            five(row, col, -1, 1, color) ||
            five(row, col, 1, -1, color))
        {
            // 任意一个方向为真，则五星连珠，设定返回值
            return color == CHESS_WHITE ? _white_id : _black_id;
        }
        return 0;
    }

public:
    room(uint64_t room_id, user_table *tb_user, online_manager *online_user)
        : _room_id(room_id),
          _tb_user(tb_user),
          _online_user(online_user),
          _board(BOARD_ROW, std::vector<int>(BOARD_COL, 0)),
          _play_count(0),
          _statu(GAME_START)
    {
        LOG(DEBUG, "%lu 房间创建成功!\n", _room_id);
    }
    ~room()
    {
        LOG(DEBUG, "%lu 房间销毁成功!\n", _room_id);
    }
    uint64_t id() { return _room_id; }
    room_statu statu() { return _statu; }
    int play_count() { return _play_count; }
    void add_white_user(uint64_t userid)
    {
        _white_id = userid;
        _play_count++;
    }
    void add_black_user(uint64_t userid)
    {
        _black_id = userid;
        _play_count++;
    }
    uint64_t get_white_user() { return _white_id; }
    uint64_t get_black_user() { return _black_id; }

    // 处理下棋动作
    Json::Value handle_chess(Json::Value &req)
    {
        // 1. 定义响应
        Json::Value json_resp = req;
        // 2. 判断房间中两个玩家是否都在线,任意一个不在线,就是另一方胜利
        // 获取下棋位置,下棋id
        if (_online_user->is_in_game_room(_white_id) == false)
        {
            // 白棋不在线
            json_resp["result"] = true;
            json_resp["reason"] = "运气真好！对方掉线，不战而胜!";
            json_resp["winter"] = (Json::UInt64)_black_id;
            return json_resp;
        }
        if (_online_user->is_in_game_room(_black_id) == false)
        {
            // 黑棋不在线
            json_resp["result"] = true;
            json_resp["reason"] = "运气真好！对方掉线，不战而胜!";
            json_resp["winter"] = (Json::UInt64)_white_id;
            return json_resp;
        }
        // 3. 获取走棋位置,判断位置是否合理(位置是否已经被占用)
        int chess_row = req["row"].asInt();
        int chess_col = req["col"].asInt();
        uint64_t cur_uid = req["uid"].asUInt64();
        if (_board[chess_row][chess_col] != 0)
        {
            // 此时走棋位置已经有棋子了
            json_resp["result"] = false;
            json_resp["reason"] = "当前位置已经有棋子了";
            return json_resp;
        }
        // 此时走棋位置一定一合理的
        // 获取当前走棋的颜色
        int cur_color = cur_uid == _white_id ? CHESS_WHITE : CHESS_BLACK;
        _board[chess_row][chess_col] = cur_color;
        // 4. 判断当前是否有玩家胜利,(当前位置开始是否存在五星连珠)
        uint64_t win_id = win_check(chess_row, chess_col, cur_color);
        if (win_id != 0)
        {
            // 用户胜利逻辑
            json_resp["reason"] = "五星连珠,战无敌!";
        }
        json_resp["result"] = true;
        json_resp["winner"] = (Json::UInt64)win_id; // 当id==0时,表示没有用户胜利,否则相反
        return json_resp;
    }
    // 处理聊天动作
    Json::Value handle_chat(Json::Value &req)
    {
        Json::Value json_req = req;
        // 检测房间是否一致
        uint64_t room_id = req["room_id"].asUInt64();
        if (room_id != _room_id)
        {
            json_req["result"] = false;
            json_req["reason"] = "房间号不匹配";
            return json_req;
        }
        // 检测信息中是否有敏感词
        std::string message = req["message"].asCString();
        size_t pos = message.find("垃圾");
        if (pos != std::string::npos)
        {
            json_req["result"] = false;
            json_req["reason"] = "信息中包含敏感词，不能发送";
            return json_req;
        }
        json_req["result"] = true;
        return json_req;
    }
    // 处理玩家退出房间动作
    void handle_exit(uint64_t uid)
    {
        // 如果是下棋中退出，则对方胜利，否则下棋结束了退出，是正常退出
        Json::Value json_resp;
        if (_statu == GAME_START)
        {
            uint64_t winner_id = (Json::UInt64)(uid == _white_id ? _black_id : _white_id);
            // 此时表明是下棋中退出
            json_resp["optype"] = "put_chess";
            json_resp["result"] = true;
            json_resp["reason"] = "对方掉线，不战而胜！";
            json_resp["room_id"] = (Json::UInt64)_room_id;
            json_resp["uid"] = (Json::UInt64)uid;
            json_resp["row"] = -1;
            json_resp["col"] = -1;
            json_resp["winner"] = (Json::UInt64)winner_id;
            uint64_t loser_id = winner_id == _white_id ? _black_id : _white_id;
            _tb_user->win(winner_id);
            _tb_user->lose(loser_id);
            _statu = GAME_OVER;
            broadcast(json_resp);
        }
        // 房间中玩家数量减一
        _play_count--;
        return;
    }
    // 总的请求函数,在函数内部区分请求类型,根据不同请求调用不同的处理函数,得到响应进行广播
    void handle_request(Json::Value &req)
    {
        // 1. 校验房间是否匹配
        Json::Value json_resp;
        uint64_t root_id = req["room_id"].asUInt64();
        if (root_id != _room_id)
        {
            json_resp["optype"] = req["optype"].asString();
            json_resp["result"] = false;
            json_resp["reason"] = "房间号不匹配！";
            return broadcast(json_resp);
        }
        // 2. 根据不同请求类型调用不同处理函数
        if (req["optype"].asCString() == "put_chess")
        {
            // 此时是下棋请求
            json_resp = handle_chess(req);
            if (json_resp["winner"].asInt64() != 0)
            {
                // 此时表明有人胜利了
                uint64_t winner_id = json_resp["winner"].asInt64();
                uint64_t loser_id = winner_id == _white_id ? _black_id : _white_id;
                _tb_user->win(winner_id);
                _tb_user->lose(loser_id);
                _statu = GAME_OVER;
            }
        }
        else if (req["optype"].asCString() == "chat")
        {
            // 此时是聊天请求
            json_resp = handle_chat(req);
        }
        else
        {
            // 无法判断请求类型
            json_resp["optype"] = req["optype"].asCString();
            json_resp["result"] = false;
            json_resp["reason"] = "未知请求类型";
        }
        std::string body;
        json_util::serialize(json_resp, body);
        LOG(DEBUG, "房间-广播动作：%s\n", body.c_str());
        return broadcast(json_resp);
    }
    // 将指定信息广播给房间中所有玩家
    void broadcast(Json::Value &req)
    {
        // 1. 对要响应的信息进行序列化，将Json::Value中数据序列化为json格式的字符串
        std::string body;
        json_util::serialize(req, body);
        // 2. 获得房间中所有用户的通信连接
        // 3. 发送响应信息
        websocketsvr::connection_ptr wconn = _online_user->get_conn_from_room(_white_id);
        if (wconn.get() != nullptr)
        {
            wconn->send(body);
        }
        else
        {
            LOG(DEBUG, "房间-白棋玩家连接失败\n");
        }
        websocketsvr::connection_ptr bconn = _online_user->get_conn_from_room(_black_id);
        if (bconn.get() != nullptr)
        {
            bconn->send(body);
        }
        else
        {
            LOG(DEBUG, "房间-白棋玩家连接失败\n");
        }
        return;
    }
};
using room_ptr = std::shared_ptr<room>; // 房间信息

class room_manage
{
private:
    uint64_t _next_rid;                            // 房间id
    std::mutex _mutex;                             // 互斥锁
    user_table *_tb_user;                          // 数据管理模块的句柄
    online_manager *_online_user;                  // 在线用户管理模块句柄
    std::unordered_map<uint64_t, room_ptr> _rooms; // 管理房间ID和房间信息映射
    std::unordered_map<uint64_t, uint64_t> _users; // 管理用户ID和和房间ID映射
public:
    // 初始化房间ID计数器
    room_manage(user_table* ut,online_manager* om)
    :_next_rid(1)
    ,_tb_user(ut)
    ,_online_user(om)
    {
        LOG(DEBUG,"房间管理模块初始化完毕!\n");
    }
    ~room_manage()
    {
        LOG(DEBUG,"房间管理模块即将销毁!\n");
    }
    //为两个用户房间,并返回房间的智能指针管理对象
    room_ptr cteate_room(uint64_t uid1,uint64_t uid2)
    {
        //1. 两个用户在游戏大厅进行配配对战,只有匹配成功后创建房间
        if(_online_user->is_in_game_hall(uid1) == false)
        {
            LOG(DEBUG,"用户:%lu不在大厅中,创建房间失败!\n");
            return room_ptr();
        }
        if(_online_user->is_in_game_hall(uid2) == false)
        {
            LOG(DEBUG,"用户:%lu,不在大厅中,创建房间失败!\n");
            return room_ptr();
        }
        //2. 创建房间,将用户信息添加到房间中
        //加锁
        std::unique_lock<std::mutex> lock(_mutex);
        room_ptr rep(new room(_next_rid,_tb_user,_online_user));
        rep->add_black_user(uid1);
        rep->add_white_user(uid2);
        //将房间信息管理起来
        _rooms.insert(std::make_pair(_next_rid,rep));
        _users.insert(std::make_pair(uid1,_next_rid));
        _users.insert(std::make_pair(uid2,_next_rid));
        _next_rid++;
        //返回房间信息
        return rep;
    }
    //通过房间ID获取房间信息
    room_ptr get_room_by_rid(uint64_t rid)
    {
        //加锁
        std::unique_lock<std::mutex> lock(_mutex);
        auto it = _rooms.find(rid);
        if(it == _rooms.end())
        {
            return room_ptr();
        }
        return it->second;
    }
    //通过用户ID获取房间信息
    room_ptr get_room_by_uid(uint64_t uid)
    {
        //加锁
        std::unique_lock<std::mutex> lock(_mutex);
        //1. 通过房间ID获取房间ID
        auto uit = _users.find(uid);
        if(uit == _users.end())
        {
            return room_ptr();
        }
        uint64_t rid = uit->second;
        //2. 通过房间ID获取房间信息
        auto rit = _rooms.find(rid);
        if(rit == _rooms.end())
        {
            return room_ptr();
        }
        return rit->second;
    }
    //通过房间ID获取房间信息
    void remove_room(uint64_t rid)
    {
        //因为房间信息时通过shared_ptr管理在_rooms中,因此只要将shared_ptr从_rooms中移除
        //则shared_ptr计数器==0,外界没有对房间信息进行操作保存的情况下会被释放
        //1. 通过房间ID,获取房间信息
        room_ptr rp = get_room_by_rid(rid);
        if(rp.get() == nullptr)
        {
            //当指针所管理的内容为空时
            return;
        } 
        //2. 通过房间信息,获取房间中所有用户的ID
        uint64_t uid1 = rp->get_white_user();
        uint64_t uid2 = rp->get_black_user();
        //3. 移除房间管理中的用户信息
        _users.erase(uid1);
        _users.erase(uid2);
        //4. 移除房间管理信息
        _rooms.erase(rid);
    }
    //删除房间中指定用户,如果房间中没有用户了,则销毁房间,用户连接断开时被调用
    void remove_room_user(uint64_t uid)
    {
        //加锁
        std::unique_lock<std::mutex> lock(_mutex);
        room_ptr rp = get_room_by_uid(uid);
        if(rp == nullptr)
        {
            return;
        }
        //处理房间中玩家退出操作
        rp->handle_exit(uid);
        //当房间中没有玩家了,则销毁房间
        if(rp->play_count() == 0)
        {
            remove_room(rp->id());
        }
    }
};